"""
CBMPy: Constraint Based Modelling in Python (http://pysces.sourceforge.net/cbm)
============
Copyright (C) 2009-2012 Brett G. Olivier, VU University Amsterdam, Amsterdam, The Netherlands

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>

Author: Brett G. Olivier
Contact email: bgoli@users.sourceforge.net
Last edit: $Author: bgoli $ ($Id: CBModelTools.py 990 2012-02-15 13:52:11Z bgoli $)

"""

from CBVersion import __DEBUG__, __version__

import re, numpy
import CBModel as CBModel

def addReversibilityBounds(reactions, bounds, infinity=numpy.inf):
    bounds = bounds.copy()
    for R in reactions:
        if not bounds.has_key(reactions[R]['id']):
            if not reactions[R]['reversible']:
                bounds.update({reactions[R]['id'] : {'lower' : 0, 'upper' : infinity}})
            else:
                bounds.update({reactions[R]['id'] : {'lower' : -infinity, 'upper' : infinity}})
        else:
            print 'Reaction %s already has bounds: %s' % (reactions[R]['id'], str(reactions[R]))
    return bounds

def addReversibilityBoundsIgnoreReversible(reactions, bounds):
    bounds = bounds.copy()
    for R in reactions:
        if not bounds.has_key(reactions[R]['id']):
            if not reactions[R]['reversible']:
                bounds.update({reactions[R]['id'] : {'lower' : 0}})
        else:
            print 'Reaction %s already has bounds: %s' % (reactions[R]['id'], str(reactions[R]))
    return bounds

def addSpecies(model, species):
    skeys = species.keys()
    skeys.sort()
    for S in skeys:
        comp = 'Cell'
        id = name = species[S]['id']
        if species[S].has_key('compartment'):
            comp = species[S]['compartment']
        if species[S].has_key('name'):
            name = species[S]['name']
        sObj = CBModel.Species(id, boundary=species[S]['boundary'],\
                            name=name, value=0, compartment=comp)
        if species[S].has_key('SUBSYSTEM'):
            sObj.annotation.update({'SUBSYSTEM' : species[S]['SUBSYSTEM']})
        else:
            sObj.annotation.update({'SUBSYSTEM' : 'metabolism'})
        model.addSpecies(sObj)

def addBounds(model, bounds):
    cntr = 0
    for B in bounds:
        if bounds[B].has_key('lower'):
            model.addFluxBound(CBModel.FluxBound('%s_lb' % B, B, 'greaterEqual', bounds[B]['lower']))
            cntr += 1
        if bounds[B].has_key('upper'):
            model.addFluxBound(CBModel.FluxBound('%s_ub' % B, B, 'lessEqual', bounds[B]['upper']))            
            cntr += 1

def addReactions(model, reactions):
    specId = model.getSpeciesIds()
    rkeys = reactions.keys()
    rkeys.sort()
    for R in rkeys:
        Bounds = {}
        exchange = False
        id = name = reactions[R]['id']
        if reactions[R].has_key('lower'):
            Bounds.update({R : {'lower' : float(reactions[R]['lower'])}})
        if reactions[R].has_key('upper'):
            Bounds.update({R : {'upper' : float(reactions[R]['upper'])}})
        if reactions[R].has_key('exchange'):
            if reactions[R]['exchange'] in ['True','true','TRUE',True]:
                exchange = True
        if reactions[R].has_key('name'):
            name = reactions[R]['name']
            ##  else:
                ##  print 'Ignoring exchange status for reaction %s, %s is not one of [True, TRUE, true]' % (R, reactions[R]['exchange'])
        if len(Bounds.keys()) > 0:
            addBounds(model, Bounds)
        
        
        revers = reactions[R]['reversible']
        ##  reagents = []
        react = CBModel.Reaction(id, name=name, reversible=revers)
        for RG in reactions[R]['reagents']:
            if RG[1] in specId:
                react.addReagent(CBModel.Reagent(id+RG[1], RG[1], RG[0]))
                ##  reagents.append(CBModel.Reagent(RG[1], RG[0]))
            else:
                raise RuntimeWarning, '\n%s is not a species!' % RG[1]
                
        # more generic way of doing this
        for k in reactions[R].keys():
            if k in ['SUBSYSTEM', 'GENE_ASSOCIATION', 'EC Number', 'Equation']:
                react.annotation.update({k : reactions[R][k]})
        ##  if reactions[R].has_key('SUBSYSTEM'):
            ##  react.annotation.update({'SUBSYSTEM' : reactions[R]['SUBSYSTEM']})
        ##  else:
            ##  react.annotation.update({'SUBSYSTEM' : 'metabolism'})
        react.is_exchange = exchange
        model.addReaction(react)

def addObjectiveFunction(model, objective_function):
    for o in objective_function:
        objf = objective_function[o]
        id = objf['id']
        flux = objf['flux']
        coef = objf['coefficient']
        sense = objf['sense']
        active = objf['active']
        fObj = CBModel.FluxObjective(flux+'_obj', reaction=flux, coefficient=coef)    
        objF = CBModel.Objective(id, sense)
        objF.addFluxObjective(fObj)
        model.addObjective(objF, active=active)

def quickDefaultBuild(model_name, Reactions, Species, Bounds, Objective_function, infinity=numpy.inf):
    fba = CBModel.Model(model_name)
    addSpecies(fba, Species)
    addReactions(fba, Reactions)
    addObjectiveFunction(fba, Objective_function)
    Bounds = addReversibilityBounds(Reactions, Bounds, infinity)
    addBounds(fba, Bounds)
    return fba
    
def printSolution(fba, wait=False):
    for R in fba.reactions:
        print R.getPid(), R.value,
        if  R.annotation.has_key('SUBSYSTEM'):
            print R.annotation['SUBSYSTEM']
        else:
            print ''
    if wait: raw_input('Press <enter> to continue ...')
